📝 Резюме · 📄 Оригинал (1.2 KB)
https://t.me/Python_libr/3362

Функция exec() в Python, выполняет блок кода

Источник: https://t.me/Python_libr/3362


Что такое exec()?

exec() — встроенная функция для динамического выполнения кода Python во время работы программы. Отличается от eval() тем, что может выполнять полные блоки кода (циклы, функции, классы), а не только выражения.

Синтаксис и аргументы

exec(object, globals=None, locals=None)
  • object: строка, объект байт-кода или объект файла с Python кодом
  • globals: словарь для глобального пространства имён (опционально)
  • locals: словарь для локального пространства имён (опционально)
  • Возвращаемое значение: всегда None

Примеры: строковый код

# Простое выполнение
code = """
x = 10
y = 20
result = x + y
print(f'Результат: {result}')
"""

exec(code)
# Выведет: Результат: 30

# Выполнение с несколькими операторами
code = """
for i in range(3):
    print(f'Итерация {i}')
"""

exec(code)
# Выведет:
# Итерация 0
# Итерация 1
# Итерация 2

Использование глобального и локального пространств

# Создаём собственные пространства имён
globals_dict = {'x': 5, 'y': 10}
locals_dict = {}

code = """
z = x + y
result = z * 2
"""

exec(code, globals_dict, locals_dict)

print(locals_dict)
# {'result': 30}
print(globals_dict['z'])
# 30

Определение функций и классов

# Определение функции динамически
code = """
def greeting(name):
    return f'Привет, {name}!'

class Calculator:
    def add(self, a, b):
        return a + b
"""

namespace = {}
exec(code, namespace)

# Используем определённую функцию
greet_func = namespace['greeting']
print(greet_func('Мария'))  # Привет, Мария!

# Используем определённый класс
Calculator = namespace['Calculator']
calc = Calculator()
print(calc.add(5, 3))  # 8

Компиляция кода перед выполнением

# compile() преобразует код в объект для повторного выполнения
code_str = """
total = 0
for i in range(1, 6):
    total += i
print(f'Сумма: {total}')
"""

# Компилируем один раз
compiled_code = compile(code_str, '<string>', 'exec')

# Выполняем скомпилированный код несколько раз
exec(compiled_code)  # Сумма: 15
exec(compiled_code)  # Сумма: 15

# Это быстрее, чем выполнять строку несколько раз

Практический пример: интерпретатор выражений

def simple_calculator(expression_list):
    """Выполняет список выражений"""
    namespace = {}

    for expr in expression_list:
        try:
            code = f"{expr}"
            exec(code, namespace)
            # Выводим локальные переменные
            if 'result' in namespace:
                print(f"{expr} => {namespace['result']}")
        except Exception as e:
            print(f"Ошибка: {e}")

    return namespace

# Использование
expressions = [
    "x = 10",
    "y = 20",
    "result = x + y",
    "result = result * 2"
]

final_vars = simple_calculator(expressions)
print(final_vars)
# {'x': 10, 'y': 20, 'result': 60}

Диаграмма выполнения

graph LR
    A["Строка кода<br/>или compile()"] --> B{"exec()<br/>функция"}
    B --> C["Парсинг<br/>синтаксиса"]
    C --> D["Компиляция<br/>в bytecode"]
    D --> E["Выполнение<br/>в VM"]
    E --> F["Возврат None"]

Отличие exec() от eval()

# eval() - работает только с выражениями
result = eval("2 + 3")
print(result)  # 5

# eval с ошибкой - не может выполнить блок кода
try:
    eval("x = 5")  # SyntaxError!
except SyntaxError as e:
    print(f"eval не поддерживает присваивание: {e}")

# exec() - работает с полными блоками
exec("x = 5")
# Работает без проблем

# exec() не возвращает значение
result = exec("2 + 3")
print(result)  # None

# Нужно явно получить переменную
namespace = {}
exec("result = 2 + 3", namespace)
print(namespace['result'])  # 5

Опасности и безопасность

# ОПАСНО: выполнять пользовательский код без проверки
user_input = "__import__('os').system('rm -rf /')"
# exec(user_input)  # Не делайте так!

# БЕЗОПАСНО: ограничить доступные функции
safe_dict = {
    '__builtins__': {
        'print': print,
        'len': len,
        'range': range,
        'str': str,
        'int': int
    }
}

# Пользователь может использовать только эти функции
user_code = "print(sum(range(10)))"
exec(user_code, safe_dict)  # print: 45

# Опасные операции недоступны
try:
    dangerous = "__import__('os')"
    exec(dangerous, safe_dict)
except:
    print("__import__ недоступен - защита работает!")

Лучшие практики

  1. Избегайте exec() если возможно — обычно есть лучшее решение
  2. Никогда не выполняйте пользовательский код без строгой валидации
  3. Используйте ограниченное пространство имён для безопасности
  4. Документируйте динамический код — это сложнее для отладки
  5. Кешируйте скомпилированный код если выполняете его много раз
  6. Обрабатывайте исключения — синтаксические ошибки приведут к сбоям